package com.qire.manhua.service;

import android.app.Activity;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;

import com.lzy.okgo.OkGo;
import com.lzy.okgo.convert.Converter;
import com.lzy.okgo.model.Progress;
import com.lzy.okgo.utils.HttpUtils;
import com.lzy.okgo.utils.IOUtils;
import com.qire.common.SummerApp;
import com.qire.common.basic.ObjectUtil;
import com.qire.common.concurrent.ThreadUtil;
import com.qire.common.utils.FileManager;
import com.qire.manhua.R;
import com.qire.manhua.dal.dao.ComicChapterDao;
import com.qire.manhua.dal.dao.ComicDatabase;
import com.qire.manhua.model.po.LocalComicChapter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import androidx.annotation.Nullable;
import okhttp3.ResponseBody;

/**
 * 漫画下载服务。下载章节状态遵守只有下载服务器修改刷新，这样可以规避一些线程数据安全的问题。
 */
public class ComicDownloadService extends Service {

    /** 没有操作 */
    public static final int ACTION_NONE = 0;
    /** 开始下载 */
    public static final int ACTION_START = 1;
    /** 暂停下载 */
    public static final int ACTION_PAUSE = 2;

    /** 下载进度观察者 */
    private final ArrayList<ProgressObserver> observers = new ArrayList<>();

    /** 下载绑定器 */
    private final ComicDownloadBinder binder = new ComicDownloadBinder();

    /** 任务 {@code Map<chapterId, chapter>} */
    private final TreeMap<String, ComicDownloadTask> taskMap = new TreeMap<>();

    /** 下载线程池：使用3个大小的固定线程池。 */
    private final ThreadPoolExecutor fixedThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

    /** 数据库操作 */
    private final ComicChapterDao comicChapterDao = ComicDatabase.database().comicChapterDao();

    /**
     * 启动下载服务
     * @param application 应用对象实例
     * @param downloadActionFlags 下载行为标志： {@link #ACTION_START} 或 {@link #ACTION_PAUSE} 默认行为 {@link #ACTION_NONE}
     * @param chapterIds 下载章节ID集合
     */
    public static void startService(Application application, int downloadActionFlags, int... chapterIds) {
        try {
            Intent intent = new Intent(application, ComicDownloadService.class);
            intent.putExtra("downloadActionFlags", downloadActionFlags);
            intent.putExtra("chapterIds", chapterIds);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                // NOTE: android8.0后不允许后台开启服务，故使用兼容方式打开服务
                application.startForegroundService(intent);
//                ContextCompat.startForegroundService(application, intent);
            } else {
                application.startService(intent);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //https://stackoverflow.com/questions/38764497/security-exception-unable-to-start-service-user-0-is-restricted
            //这个问题的原因是OPPO手机自动熄屏一段时间后，会启用系统自带的电量优化管理，
            //禁止一切自启动的APP（用户设置的自启动白名单除外）。所以，类似的崩溃常常集中在用户休息之后的夜里或者凌晨，
            //但是并不影响用户平时的正常使用。至于会出现user 0 is restricted，我觉得是coloros系统电量优化管理做得不好的地方。
            //对coloros官方的处理建议：既然禁止自启动，那么干脆直接force stop对应的进程，而不是抛出RuntimeException来让开发者买单。
            //对开发者处理建议：在服务启动的地方进行try catch防止崩溃即可（也是“1元夺宝”APP目前的处理方式）
        }
    }

    /**
     * 停止服务
     * @param application 应用实例
     * @return
     */
    public static boolean stopService(Application application) {
        try {
            Intent intent = new Intent(application, ComicDownloadService.class);
            return application.stopService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 绑定服务
     * @param context 设备上下文
     * @param conn 服务连接接口，用于处理绑定连接及断开连接时需要处理的行为
     * @return
     */
    public static boolean bindService(Context context, ServiceConnection conn) {
        try {
            Intent intent = new Intent(context, ComicDownloadService.class);
            return context.bindService(intent, conn, Activity.BIND_AUTO_CREATE);
        } catch (Exception e) {
            e.printStackTrace();
            //https://stackoverflow.com/questions/38764497/security-exception-unable-to-start-service-user-0-is-restricted
            //这个问题的原因是OPPO手机自动熄屏一段时间后，会启用系统自带的电量优化管理，
            //禁止一切自启动的APP（用户设置的自启动白名单除外）。所以，类似的崩溃常常集中在用户休息之后的夜里或者凌晨，
            //但是并不影响用户平时的正常使用。至于会出现user 0 is restricted，我觉得是coloros系统电量优化管理做得不好的地方。
            //对coloros官方的处理建议：既然禁止自启动，那么干脆直接force stop对应的进程，而不是抛出RuntimeException来让开发者买单。
            //对开发者处理建议：在服务启动的地方进行try catch防止崩溃即可（也是“1元夺宝”APP目前的处理方式）
        }
        return false;
    }

    /**
     * 解绑服务，用于主动接触服务绑定。
     * @param context
     * @param conn
     */
    public static void unBindService(Context context, ServiceConnection conn) {
        if(ObjectUtil.nonNull(context)) {
            context.unbindService(conn);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //适配8.0service，前台服务必须绑定一个通知
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            final String CHANNEL_ID_STRING = "ComicDownloadService";
            NotificationManager notificationManager = (NotificationManager) SummerApp.summerApp().getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_STRING, getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW);
            notificationManager.createNotificationChannel(mChannel);
            Notification notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID_STRING).build();
            startForeground(1, notification);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if(ObjectUtil.nonNull(intent)) {
            int downloadActionFlags = intent.getIntExtra("downloadActionFlags", ACTION_NONE);
            int[] chapterIds = intent.getIntArrayExtra("chapterIds");
            switch (downloadActionFlags) {
                case ACTION_START :
                    handleStartAction(chapterIds);
                    break;
                case ACTION_PAUSE :
                    handlePauseAction(chapterIds);
                    break;
            }
        }

        return super.onStartCommand(intent, flags, startId);
    }

    private void handleStartAction(int... comicChapterIds) {
        ThreadUtil.<int[]>once().map(comicChapterIds).andThen(chapterIds -> {
            List<LocalComicChapter> chapterList = comicChapterDao.queryListIn(chapterIds);
            for(LocalComicChapter chapter : chapterList) {
                if(chapter.isDownloadComplete()) {
                    continue;
                }
                String idKey = String.valueOf(chapter.getId());
                ComicDownloadTask comicDownloadTask = taskMap.get(idKey);
                if(ObjectUtil.isNull(comicDownloadTask)) {
                    comicDownloadTask = new ComicDownloadTask(chapter);
                    taskMap.put(idKey, comicDownloadTask);
                    fixedThreadPool.execute(comicDownloadTask);
                }
                comicDownloadTask.start();
            }
        }).accept();
    }

    private void handlePauseAction(int... comicChapterIds) {
        ThreadUtil.<int[]>once().map(comicChapterIds).andThen(chapterIds -> {
            List<LocalComicChapter> chapterList = comicChapterDao.queryListIn(chapterIds);
            for(LocalComicChapter chapter : chapterList) {
                if(chapter.isDownloadComplete()) {
                    continue;
                }
                String idKey = String.valueOf(chapter.getId());
                ComicDownloadTask comicDownloadTask = taskMap.get(idKey);
                if(ObjectUtil.nonNull(comicDownloadTask)) {
                    taskMap.remove(idKey);
                    fixedThreadPool.remove(comicDownloadTask);
                } else {
                    comicDownloadTask = new ComicDownloadTask(chapter);
                }
                comicDownloadTask.stop();
            }
        }).accept();
    }

    private void updateObserver(LocalComicChapter comicChapter) {
        for(ProgressObserver observer : observers) {
            observer.update(comicChapter);
        }
    }

    /**
     * 漫画下载绑定器
     */
    public class ComicDownloadBinder extends Binder {

        public void addObserver(ProgressObserver observer) {
            observers.add(observer);
        }

        public void removeObserver(ProgressObserver observer) {
            observers.remove(observer);
        }

    }

    /**
     * 漫画下载任务
     */
    private final class ComicDownloadTask implements Runnable, Converter<File> {

        private final LocalComicChapter comicChapter;
        private final File chapterDir;

        private volatile boolean isStop = false;

        private ComicDownloadTask (LocalComicChapter comicChapter) {
            this.comicChapter = comicChapter;
            this.chapterDir = FileManager.allotChapterFolder(comicChapter.getComicId(), comicChapter.getId());
        }

        private void start() {
            // 进入下载状态,并保存状态到数据库
            comicChapter.setDownloadState(LocalComicChapter.STATE_RUNNING);
            comicChapterDao.update(comicChapter);
            updateObserver(comicChapter);
        }

        private void stop() {
            isStop = true;
            comicChapter.setDownloadState(LocalComicChapter.STATE_PAUSE);
            comicChapterDao.update(comicChapter);
            updateObserver(comicChapter);
        }

        @Override
        public void run() {
            // 1.如果下载完成的不在处理
            if(comicChapter.isDownloadComplete()) {
                return;
            }

            // 2.记录章节下载所用到的数据库信息
            final int           lastDownloadCount   = comicChapter.getDownloadCount();
            final List<String>  imageContentList    = comicChapter.getContentList();

            // 3.本次下载需要的数据信息
            int     downloadCount   = 0;
            boolean isComplete      = true;

            // 4.检查章节目录是否有效，无效则重新创建
            IOUtils.createFolder(chapterDir);

            // 5.下载未下载文件并严格检查有效性
            for (int index = lastDownloadCount; index < imageContentList.size(); index++) {
                // 暂停,下载中断
                if(isStop) {
                    isComplete = false;
                    break;
                }
                // 网络下载流程
                try {
                    String imageUrl = imageContentList.get(index);
                    File file = OkGo.<File>get(imageUrl).converter(this).adapt().execute().body();
                    if(FileManager.exists(file)) {
                        comicChapter.setDownloadCount(index + 1);
                        updateObserver(comicChapter);
                    } else {
                        // 某个图片下载失败， 下载中断
                        isComplete = false;
                        break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    isComplete = false;
                    // 需要处理URL失效的问题
                }
            }

            // 6.检查已下载文件是否有效，下载服务中不在检查文件受损情况，由于第一次下载总会检查完整性，
            // 所以下载过程中不会存在不无效图片，由于使用app应用空间所以不会被未root的用户修改，
            // 排除上述问题，可以在阅读页图片读取显示时增加一个检查机制来规避后续被破坏的问题。
            // 对于已下载漫画，本地已有完整数据，所以同样可以保证漫画数据的完整性但可能于服务器数据有所不同。
            // 固只有下载中服务器数据与本地数据有差异时会存在问题，比如图片URL已保存，但服务器发生修改导致URL失效
            // 下载过程中无法完成图片下载，引发的问题。
//            FileManager.checkBitmapFileIsValid(file)

            int state = isComplete ? LocalComicChapter.STATE_COMPLETE : LocalComicChapter.STATE_PAUSE;
            comicChapter.setDownloadState(state);
            comicChapterDao.update(comicChapter);
            updateObserver(comicChapter);
        }

        @Override
        public File convertResponse(okhttp3.Response response) throws Throwable {
            // 获取请求的url
            String url = response.request().url().toString();
            // 获得文件名称
            String fileName = HttpUtils.getNetFileName(response, url);
            // 创建图片本地保存存文件引用
            File file = new File(chapterDir, fileName);
            // 进入下载图片流程，如果文件位置存在则删除文件。开启下载
            IOUtils.delFileOrFolder(file);

            // 下载流读取过程
            InputStream bodyStream = null;
            byte[] buffer = new byte[8192];
            FileOutputStream fileOutputStream = null;
            try {
                ResponseBody body = response.body();
                if (body == null) return null;

                bodyStream = body.byteStream();
                Progress progress = new Progress();
                progress.totalSize = body.contentLength();
                progress.fileName = fileName;
                progress.filePath = file.getAbsolutePath();
                progress.status = Progress.LOADING;
                progress.url = url;
                progress.tag = url;

                int len;
                fileOutputStream = new FileOutputStream(file);
                while ((len = bodyStream.read(buffer)) != -1) {
                    fileOutputStream.write(buffer, 0, len);

                    // 单张图片下载字节进度更新，这里目前不需要固注释，若有需求可恢复
//                    if (callback == null) continue;
//                    Progress.changeProgress(progress, len, new Progress.Action() {
//                        @Override
//                        public void call(Progress progress) {
//                            onProgress(progress);
//                        }
//                    });
                }
                // 流读取结束，强制刷新缓存流写入文件，下载完成。
                fileOutputStream.flush();

                // 检查文件是否为有效 bitmap, 保证图片一定是完整有效可以显示的。
                if(!FileManager.checkBitmapFileIsValid(file)) {
                    // 文件受损无效，删除当前文件返回null。
                    file.delete();
                    return null;
                }

                // 返回正常文件
                return file;
            } finally {
                IOUtils.closeQuietly(bodyStream);
                IOUtils.closeQuietly(fileOutputStream);
            }
        }

    }

    /**
     * 下载进度观察者
     */
    public interface ProgressObserver {
        /**
         * 进度或状态发生改变时会触发该方法
         * @param comicChapter 目标下载章节实例
         */
        void update(LocalComicChapter comicChapter);

    }

}
